<?php
/**
 * Product: proxy_server.
 * Date: 2021-11-12
 * Time: 10:21
 */

namespace ASW\Utility\Buffer;

class ReadableBuffer extends Buffer
{
    private static int $_floatSize  = 0;
    private static int $_doubleSize = 0;

    public function readString(): string|false
    {
        if (false === $len = $this->readUChar()) return false;
        return $this->read($len);
    }

    public function readUChar(): int|false
    {
        return $this->readUnPack('C');
    }

    private function readUnPack(string $mode): string|false
    {
        if (false === $packSize = $this->getPackSize($mode)) {
            $this->setLastError("pack mode $mode unsupported");
            return false;
        }
        if (false === $bin = $this->read($packSize)) return false;
        if (false === $unpackResult = unpack($mode . 'v', $bin)) {
            $hex = bin2hex($bin);
            $this->setLastError("bin [$hex] unpack to mode $mode fail");
            return false;
        }
        return $unpackResult['v'];
    }

    private function getPackSize(string $mode): int|false
    {
        return match ($mode) {
            'c', 'C' => 1,
            's', 'S', 'n', 'v' => 2,
            'i', 'I' => PHP_INT_SIZE,
            'l', 'L', 'N', 'V' => 4,
            'q', 'Q', 'J', 'P' => 8,
            'f', 'g', 'G' => self::getFloatSize(),
            'd', 'e', 'E' => self::getDoubleSize(),
            default => false,
        };
    }

    private static function getFloatSize(): int
    {
        if (self::$_floatSize <= 0) self::$_floatSize = strlen(pack('f', 1));
        return self::$_floatSize;
    }

    private static function getDoubleSize(): int
    {
        if (self::$_doubleSize <= 0) self::$_doubleSize = strlen(pack('d', 1));
        return self::$_doubleSize;
    }

    public function read(int $size = 1): string|false
    {
        $leftSize = $this->size() - $this->position();
        if ($size > $leftSize) {
            $this->setLastError("want read $size more then left size $leftSize");
            return false;
        }

        $rtn = substr($this->_content, $this->position(), $size);
        $this->move($size);
        return $rtn;
    }

    public function readLine(bool $withLineEnd = false): string|false
    {
        if ($this->isEof()) return false;
        $lineEndPos = strpos($this->_content, "\n", $this->position());
        $line       = $lineEndPos === false ? $this->readToEnd() : $this->read($lineEndPos + 1 - $this->position());
        return $withLineEnd ? $line : trim($line, "\r\n");
    }

    public function readToEnd(): string|false
    {
        return $this->read($this->size() - $this->position());
    }

    public function readByte(): string|false
    {
        return $this->read(1);
    }

    public function readChar(): int|false
    {
        return $this->readUnPack('c');
    }

    public function readInt16(): int|false
    {
        return $this->readUnPack('s');
    }

    public function readUInt16(): int|false
    {
        return $this->readUnPack('S');
    }

    public function readUInt16BE(): int|false
    {
        return $this->readUnPack('n');
    }

    public function readUInt16LE(): int|false
    {
        return $this->readUnPack('v');
    }

    public function readInt(): int|false
    {
        return $this->readUnPack('i');
    }

    public function readUInt(): int|false
    {
        return $this->readUnPack('I');
    }

    public function readInt32(): int|false
    {
        return $this->readUnPack('l');
    }

    public function readUInt32(): int|false
    {
        return $this->readUnPack('L');
    }

    public function readUInt32BE(): int|false
    {
        return $this->readUnPack('N');
    }

    public function readUInt32LE(): int|false
    {
        return $this->readUnPack('V');
    }

    public function readInt64(): int|false
    {
        return $this->readUnPack('q');
    }

    public function readUInt64(): int|false
    {
        return $this->readUnPack('Q');
    }

    public function readUInt64BE(): int|false
    {
        return $this->readUnPack('J');
    }

    public function readUInt64LE(): int|false
    {
        return $this->readUnPack('P');
    }

    public function readFloat(): float|false
    {
        return $this->readUnPack('f');
    }

    public function readFloatBE(): float|false
    {
        return $this->readUnPack('G');
    }

    public function readFloatLE(): float|false
    {
        return $this->readUnPack('g');
    }

    public function readDouble(): float|false
    {
        return $this->readUnPack('d');
    }

    public function readDoubleBE(): float|false
    {
        return $this->readUnPack('E');
    }

    public function readDoubleLE(): float|false
    {
        return $this->readUnPack('e');
    }
}